﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace BesAsm.Stormwater.Swmm5Results
{
  public class OutputFileReader
  {
    private string _file;
    public string File { get { return _file; } }
    public int BytesPerRow { get { return _bytesPerRow; } }
    public int ResultsCount { get { return _simResults.ResultsCount; } }
    public int ResultsPosition { get { return _resultsPosition; } }
    public bool IsOpen { get; private set; }
    public DateTime StartDate { get { return _simResults.StartDate; } }
    public DateTime EndDate { get { return _simResults.EndDate; } }
    public TimeSpan Interval { get { return _simResults.Interval; } }

    private BinaryReader _reader;
    private const int FILE_CODE = 516114522;

    private int _id;
    private int _version;
    public int Version { get { return _version; } }
    private int _unit;
    public int Unit { get { return _unit; } }

    private int _openingPosition;
    private int _propertiesPosition;
    private int _resultsPosition;
    private int _bytesPerRow;

    private int _errorCode;

    private SimulationResults _simResults;

    private int _catchmentVarCount, _nodeVarCount, _linkVarCount, _systemVarCount;

    private Dictionary<string, int> _catchmentPosition, _nodePosition, _linkPosition;
    internal Dictionary<string, int> CatchmentPosition { get { return _catchmentPosition; } }
    internal Dictionary<string, int> NodePosition { get { return _nodePosition; } }
    internal Dictionary<string, int> LinkPosition { get { return _linkPosition; } }

    public OutputFileReader(string file)
    {
      _file = file;
    }

    public SimulationResults Read()
    {
      _simResults = new SimulationResults(this);
      bool isOpened = false;
      try
      {
        isOpened = OpenReader();
        ReadClosingRecords();
        ReadOpeningRecords();
        ReadProperties();
        ReadResults();
      }
      catch (Exception ex)
      {
        throw ex;
      }
      finally
      {
        if (isOpened)
        CloseReader();
      }
      return _simResults;
    }

    private void ReadOpeningRecords()
    {
      _reader.BaseStream.Seek(0, SeekOrigin.Begin);
      _id = _reader.ReadInt32();
      Trace.Assert(_id == FILE_CODE);
      _version = _reader.ReadInt32();
      _unit = _reader.ReadInt32();

      int catchmentCount = _reader.ReadInt32();
      int nodeCount = _reader.ReadInt32();
      int linkCount = _reader.ReadInt32();
      int pollutantCount = _reader.ReadInt32();

      _simResults.CatchmentCount = catchmentCount;
      _simResults.NodeCount = nodeCount;
      _simResults.LinkCount = linkCount;
      _simResults.PollutantCount = pollutantCount;

      string[] pollutants = new string[pollutantCount];
      int[] pollutantCodes = new int[pollutantCount];

      for (int i = 0; i < catchmentCount; i++)
      {
        int numChar = _reader.ReadInt32();
        string catchmentName = new string(_reader.ReadChars(numChar));
        _simResults.Catchments.Add(new CatchmentVariables(catchmentName, this));
      }
      for (int i = 0; i < nodeCount; i++)
      {
        int numChar = _reader.ReadInt32();
        string nodeName = new string(_reader.ReadChars(numChar));
        _simResults.Nodes.Add(new NodeVariables(nodeName, this));

      }
      for (int i = 0; i < linkCount; i++)
      {
        int numChar = _reader.ReadInt32();
        string linkName = new string(_reader.ReadChars(numChar));

        _simResults.Links.Add(new LinkVariables(linkName, this));
      }
      for (int i = 0; i < pollutantCount; i++)
      {
        int numChar = _reader.ReadInt32();
        pollutants[i] = new string(_reader.ReadChars(numChar));
      }
      for (int i = 0; i < pollutantCount; i++)
      {
        pollutantCodes[i] = _reader.ReadInt32();
      }
      return;
    }

    private void ReadProperties()
    {
      _reader.BaseStream.Seek(_propertiesPosition, SeekOrigin.Begin);

      ReadCatchmentProperties();
      ReadNodeProperties();
      ReadLinkProperties();
      ReadReportingVariables();
    }

    private void ReadReportingVariables()
    {
      int pollutantCount = _simResults.PollutantCount;
      _catchmentVarCount = _reader.ReadInt32();
      Trace.Assert(_catchmentVarCount == 6 + pollutantCount);
      for (int i = 0; i < _catchmentVarCount; i++)
      {
        int var = _reader.ReadInt32();
      }

      _nodeVarCount = _reader.ReadInt32();
      Trace.Assert(_nodeVarCount == 6 + pollutantCount);
      for (int i = 0; i < _nodeVarCount; i++)
      {
        int var = _reader.ReadInt32();
      }

      _linkVarCount = _reader.ReadInt32();
      Trace.Assert(_linkVarCount == 5 + pollutantCount);
      for (int i = 0; i < _linkVarCount; i++)
      {
        int var = _reader.ReadInt32();
      }

      _systemVarCount = _reader.ReadInt32();
      Trace.Assert(_systemVarCount == 14);
      for (int i = 0; i < _systemVarCount; i++)
      {
        int var = _reader.ReadInt32();
      }

      double days = _reader.ReadDouble();
      _simResults.StartDate = new DateTime(1899, 12, 30).AddDays(days);
      int seconds = _reader.ReadInt32();
      _simResults.Interval = new TimeSpan(0, 0, seconds);
      _simResults.EndDate = _simResults.StartDate.AddSeconds(
        _simResults.Interval.TotalSeconds * _simResults.ResultsCount);

      return;
    }

    private void ReadLinkProperties()
    {
      int linkPropertyCount = _reader.ReadInt32();
      Trace.Assert(linkPropertyCount == 5);

      int[] linkPropertyValues = new int[linkPropertyCount];

      for (int i = 0; i < linkPropertyCount; i++)
        linkPropertyValues[i] = _reader.ReadInt32();

      int linkCount = _simResults.LinkCount;

      for (int i = 0; i < linkCount; i++)
      {
        LinkVariables link = _simResults.Links[i];

        link.LinkType = _reader.ReadSingle();
        link.UsInvertOffset = _reader.ReadSingle();
        link.DsInvertOffset = _reader.ReadSingle();
        link.MaxDepth = _reader.ReadSingle();
        link.Length = _reader.ReadSingle();
      }
    }

    private void ReadNodeProperties()
    {
      int nodePropertyCount = _reader.ReadInt32();
      Trace.Assert(nodePropertyCount == 3);

      int[] nodePropertyValues = new int[nodePropertyCount];

      for (int i = 0; i < nodePropertyCount; i++)
        nodePropertyValues[i] = _reader.ReadInt32();

      int nodeCount = _simResults.NodeCount;

      for (int i = 0; i < nodeCount; i++)
      {
        NodeVariables node = _simResults.Nodes[i];

        node.NodeType = _reader.ReadSingle();
        node.Invert = _reader.ReadSingle();
        node.MaxDepth = _reader.ReadSingle();
      }
    }

    private void ReadCatchmentProperties()
    {
      int catchmentPropertyCount = _reader.ReadInt32();
      Trace.Assert(catchmentPropertyCount == 1);

      int[] catchmentPropertyValues = new int[catchmentPropertyCount];
      for (int i = 0; i < catchmentPropertyCount; i++)
        catchmentPropertyValues[i] = _reader.ReadInt32();

      int catchmentCount = _simResults.CatchmentCount;

      for (int i = 0; i < catchmentCount; i++)
      {
        CatchmentVariables catchment = _simResults.Catchments[i];
        catchment.Area = _reader.ReadSingle();
      }
    }

    private void ReadResults()
    {
      _reader.BaseStream.Seek(_resultsPosition, SeekOrigin.Begin);

      int linkCount = _simResults.LinkCount;
      int nodeCount = _simResults.NodeCount;
      int catchmentCount = _simResults.CatchmentCount;

      _catchmentPosition = new Dictionary<string, int>(catchmentCount);
      _nodePosition = new Dictionary<string, int>(nodeCount);
      _linkPosition = new Dictionary<string, int>(linkCount);

      int elementCount = linkCount * _linkVarCount +
         nodeCount * _nodeVarCount +
         catchmentCount * _catchmentVarCount +
         _systemVarCount;

      _bytesPerRow = 8 + (4 * elementCount);

      int i = 8 + _resultsPosition;
      foreach (CatchmentVariables cts in _simResults.Catchments)
      {
        _catchmentPosition.Add(cts.Name, i);
        i += 4 * _catchmentVarCount;
      }
      foreach (NodeVariables nts in _simResults.Nodes)
      {
        _nodePosition.Add(nts.Name, i);
        i += 4 * _nodeVarCount;
      }
      foreach (LinkVariables lts in _simResults.Links)
      {
        _linkPosition.Add(lts.Name, i);
        i += 4 * _linkVarCount;
      }
      i += _systemVarCount * 4;
      Trace.Assert(i == _resultsPosition + _bytesPerRow);
    }

    private void ReadClosingRecords()
    {
      _reader.BaseStream.Seek(-24, SeekOrigin.End);
      _openingPosition = _reader.ReadInt32();
      _propertiesPosition = _reader.ReadInt32();
      _resultsPosition = _reader.ReadInt32();
      _simResults.ResultsCount = _reader.ReadInt32();

      _errorCode = _reader.ReadInt32();
      Trace.Assert(_errorCode == 0);

      int code = _reader.ReadInt32();
      Trace.Assert(code == FILE_CODE);

    }

    public bool OpenReader()
    {
      try
      {
        Stream stream = new FileStream(_file, FileMode.Open, FileAccess.Read, FileShare.Delete, 8);
        _reader = new BinaryReader(stream);
        IsOpen = true;
      }
      catch (Exception ex)
      {
        throw ex;

      }
      return IsOpen;
    }

    public void CloseReader()
    {
      if (_reader != null)
        _reader.Close();
      IsOpen = false;
    }

    public long Seek(long position)
    {
      return _reader.BaseStream.Seek(position, SeekOrigin.Begin);
    }

    public float ReadSingle()
    {
      return _reader.ReadSingle();
    }

    public float ReadSingleAndAdvance(byte[] bytes, int offset)
    {
      _reader.BaseStream.Read(bytes, 0, 4);
      _reader.BaseStream.Seek(offset, SeekOrigin.Current);
      float f = BitConverter.ToSingle(bytes, 0);
      return f;
    }

    public float ReadSingle(int position)
    {
      Seek(position);
      return ReadSingle();
    }

    public double ReadDouble()
    {
      return _reader.ReadDouble();
    }
    public double ReadDouble(int position)
    {
      Seek(position);
      return _reader.ReadDouble();
    }

    public int ReadInt32()
    {
      return _reader.ReadInt32();
    }
    public int ReadInt32(int position)
    {
      Seek(position);
      return ReadInt32();
    }



  }
}
